from ovzdusi_entities import *
from datetime import datetime, timedelta, tzinfo
from google.appengine.api import memcache, taskqueue
from google.appengine.ext import db
from math import floor


class CacheManager:

    CACHE_KEY_WEEKDATA_OBJ = "week.data.obj"
    CACHE_KEY_WEEKDATA_STR = "week.data.str"
    CACHE_KEY_MONTHDATA_OBJ = "month.data.obj"
    CACHE_KEY_MONTHDATA_STR = "month.data.str"
    CACHE_KEY_MEASUEREPOINTS = "measure.points"

    def getWeekData(self):
        result = memcache.get(self.CACHE_KEY_WEEKDATA_STR)
        if result is None or len(result) == 0:
            data = self.getWeekDataObj()
            dataStr = self.prepareDataStr(data, "_data_week", "_start_date")
            if dataStr is None:
                return ''
            result = dataStr.encode('utf-8')
            memcache.add(self.CACHE_KEY_WEEKDATA_STR, result)
        return result

    def getMonthData(self):
        result = memcache.get(self.CACHE_KEY_MONTHDATA_STR)
        if result is None or len(result) == 0:
            #taskqueue.add(name = 'datamonth', url = '/tasks/datamonth', method = 'GET')
            return ""
        return result

    def updateWeekDataZeros(self, noZeros):
        data = self.getWeekDataObj()
        dict = data[0]
        for p in noZeros:
            if p.measurePoint.name not in dict:
                continue
            values = dict[p.measurePoint.name]
            lastIndex = len(values) - 1
            if values[lastIndex] == 0.0:
                values[lastIndex] = int(p.PM10)
        
        memcache.set(self.CACHE_KEY_WEEKDATA_OBJ, data)
        result = self.prepareDataStr(data, "_data_week", "_start_date").encode('utf-8')
        memcache.set(self.CACHE_KEY_WEEKDATA_STR, result)

    def updateData(self, add):
        self.updateWeekData(add)
        #self.updateMonthData(add)

    def updateWeekData(self, add):
        data = self.getWeekDataObj()
        dict = data[0]
        for p in add:
            if p.measurePoint.name not in dict:
                continue
            values = dict[p.measurePoint.name]
            del values[0]
            values.append(int(p.PM10))

        dt = data[1] + timedelta(hours = 1)
        memcache.set(self.CACHE_KEY_WEEKDATA_OBJ, (dict, dt))
        result = self.prepareDataStr((dict, dt), "_data_week", "_start_date").encode('utf-8')
        memcache.set(self.CACHE_KEY_WEEKDATA_STR, result)

    def updateMonthData(self, add):
        if len(add) == 0:
            return
        lastDate = add[len(add) - 1].date
        month = self.getMonthDataObj()
        diff = lastDate - month[2]
        hours = int(diff.days * 24 + diff.seconds / 3600)
        if hours < 4:
            return

        week = self.getWeekDataObj()
        dictWeek = week[0]
        dictMonth = month[0]
        for key in dictWeek:
            w = dictWeek[key]
            m = dictMonth[key]
            sum = 0
            count = 0
            len_w = len(w)
            for i in range(hours):
                add = w[len_w - hours + i]
                if add > 0:
                    sum += add
                    count += 1
                if (i + 1) % 4 == 0:
                    del m[0]
                    m.append(int(round(sum / count)) if count > 0 else 0)
                    sum = 0
                    count = 0

    def getWeekDataObj(self):
        result = memcache.get(self.CACHE_KEY_WEEKDATA_OBJ)
        if result is None or len(result) == 0:
            result = self.prepareDataObj(1)
            memcache.add(self.CACHE_KEY_WEEKDATA_OBJ, result)
        return result

    def getMonthDataObj(self):
        result = memcache.get(self.CACHE_KEY_MONTHDATA_OBJ)
        if result is None or len(result) == 0:
            result = self.prepareDataObj(4)
            memcache.add(self.CACHE_KEY_MONTHDATA_OBJ, result)
        return result

    def prepareDataObj(self, weekCount):
        points = self.getMeasurePoints()
        mpLen = len(points)

        # for every week add one more hour to bypass ladder effect:
        # when displaying the values the time axis should display the date at the beginning as well as at the end of the axis
        fromDate = datetime.now(SELC()) - timedelta(days = 7 * weekCount, hours = weekCount)
        query = db.GqlQuery("select * from Pollution where date > :1 order by date asc, measurePoint", fromDate)
        polls = query.fetch((7 * weekCount * 24 + weekCount * 2) * mpLen) # weekCount * 2: one more weekCount for sure
        if len(polls) == 0:
            return None

        data = {}
        for mp in points:
            data[mp.name] = []

        self.pollution2data(polls, data, mpLen)
        if weekCount > 1:
            self.calculateDataMonth(weekCount, data)
        
        toDate = polls[len(polls) - 1].date
        return (data, fromDate, toDate)

    def prepareDataStr(self, data, varData, varStart):
        if data == None:
            return None

        list = ["var "]
        list.append(varData)
        list.append(" = [")

        separator = "\n\t"
        for k in data[0].keys():
            list.append(separator)
            list.append("{n:'")
            list.append(k)
            list.append("',v:")
            list.append(''.join(str(data[0][k])))
            list.append("}")
            separator = ",\n\t"

        list.append("\n];\n")

        d = data[1] + timedelta(hours = 1)

        list.append("var ")
        list.append(varStart)
        list.append(" = new Date(")
        list.append(str(d.year))
        list.append(",")
        list.append(str(d.month-1))
        list.append(",")
        list.append(str(d.day))
        list.append(",")
        list.append(str(d.hour))
        list.append(",0,0,0);")

        return ''.join(list)

    def pollution2data(self, polls, data, mpLen):
        mpCount = 0
        currentDate = polls[0].date
        for p in polls:
            mpCount += 1
            delta = p.date - currentDate
            diff = delta.days * 3600 * 24 + delta.seconds
            if diff > 1800: #30 minutes => next measure time
                if mpCount <= mpLen:
                    self.fixMissingValues(data)
                if diff > 5400: #90 minutes => missing data for all MPs
                    self.fixMissingHours(diff, data)
                mpCount = 1
                currentDate = p.date
            data[p.measurePoint.name].append(int(p.PM10))
        if mpCount < mpLen:
            self.fixMissingValues(data)

    def calculateDataMonth(self, weekCount, data):
        for d in data.values():
            sum = 0
            count = 0
            len_d = len(d)
            for i in range(len_d):
                if i > 0 and i % weekCount == 0:
                    d[i / weekCount - 1] = int(round(sum / count)) if count > 0 else 0
                    sum = d[i]
                    count = 1 if sum > 0 else 0
                else:
                    add = d[i]
                    if add > 0:
                        sum += add
                        count += 1
            end = int(floor(len_d / weekCount))
            del d[end:]

    def fixMissingHours(self, seconds, data):
        hours = round(seconds / 3600) - 1
        for k in data.keys():
            data[k].extend([0] * int(hours))

    def fixMissingValues(self, data):
        maxLen = 0
        for d in data.values():
            l = len(d)
            if l > maxLen:
                maxLen = l
        
        for d in data.values():
            l = len(d)
            if l < maxLen:
                d.extend([0] * (maxLen - l))

    def getMeasurePoints(self):
        result = memcache.get(self.CACHE_KEY_MEASUEREPOINTS)
        if result is None or len(result) == 0:
            query = db.GqlQuery("select * from MeasurePoint")
            result = query.fetch(100)
            memcache.add(self.CACHE_KEY_MEASUEREPOINTS, result, 86400) # 86400 == one day
        return result


class SELC(tzinfo):
    def utcoffset(self, dt):
        return timedelta(minutes = 120) + self.dst(dt)

    def tzname(self, dt):
        return "SELC"

    def dst(self, dt):
        if 4 <= dt.month <= 10:
            return timedelta(minutes = 60)
        return timedelta(0)
